import path from 'path'
import * as step from '@flow-step/step-toolkit'
import proxyquire from 'proxyquire'
import { expect } from 'chai';
import sinon from 'sinon';
import {IParams} from '../src/params'
import {checkGoVersion, generateLinterArgs} from '../src/utils'

describe('setDependentFiles and copyDependency', () => {
    let sandbox: sinon.SinonSandbox;
    let debugStub: sinon.SinonStub;
    let fsStub: {
        existsSync: sinon.SinonStub;
        chmodSync: sinon.SinonStub;
        unlinkSync: sinon.SinonStub;
        copyFileSync: sinon.SinonStub;
    }

    beforeEach(() => {
        sandbox = sinon.createSandbox();
        debugStub = sandbox.stub(step, 'debug').resolves();
        fsStub = {
            existsSync: sandbox.stub().returns(false),
            chmodSync: sandbox.stub(),
            unlinkSync: sandbox.stub(),
            copyFileSync: sandbox.stub(),
        };
    });

    afterEach(() => {
        sandbox.restore();
    });

    it('should copy dependent files and set permissions', async () => {
        const targetDir = '/root/plugin';

        const setDependentFilesProxy = proxyquire('../src/utils', {
            fs: fsStub,
        }).setDependentFiles;

        const currentDir = path.join(__dirname,'..','src');

        await setDependentFilesProxy(targetDir);
        //fsStub.copyFileSync被call了4次
        expect(fsStub.existsSync.callCount).to.equal(4)
        expect(debugStub.calledWith('set permission for diff to 755')).to.be.true
        expect(debugStub.calledWith(`copy ${path.join(currentDir,'diff')} to /root/plugin/diff`)).to.be.true
    });

    it('should delete old files if they already exist', async () => {
        const targetDir = '/root/plugin';
        fsStub.existsSync.returns(true)

        const setDependentFilesProxy = proxyquire('../src/utils', {
            fs: fsStub,
        }).setDependentFiles;
        await setDependentFilesProxy(targetDir);

        expect(fsStub.unlinkSync.callCount).to.equal(4)
    });
});

describe('checkGoVendor', () => {
    let sandbox: sinon.SinonSandbox;
    let exportEnvVarStub: sinon.SinonStub;
    let fsStub: {
        existsSync: sinon.SinonStub;
        chmodSync: sinon.SinonStub;
        unlinkSync: sinon.SinonStub;
        copyFileSync: sinon.SinonStub;
    }
    const params = {
        projectDir: '/root/project',
    } as IParams;

    beforeEach(() => {
        sandbox = sinon.createSandbox();
        fsStub = {
            existsSync: sandbox.stub().returns(false),
            chmodSync: sandbox.stub(),
            unlinkSync: sandbox.stub(),
            copyFileSync: sandbox.stub(),
        };
        exportEnvVarStub = sandbox.stub(step, 'exportEnvVar');
    });

    afterEach(() => {
        sandbox.restore()
    });

    it('should set GO111MODULE to "on" and GOFLAGS to "-mod=vendor" when vendor directory exists', () => {
        fsStub.existsSync.returns(true);

        const checkGoVendorProxy = proxyquire('../src/utils', {
            fs: fsStub,
        }).checkGoVendor;
        checkGoVendorProxy(params);

        expect(exportEnvVarStub.calledTwice).to.be.true;
        expect(exportEnvVarStub.firstCall.calledWith('GO111MODULE', 'on')).to.be.true;
        expect(exportEnvVarStub.secondCall.calledWith('GOFLAGS', '-mod=vendor')).to.be.true;
    });

    it('should set GO111MODULE to "auto" when vendor directory does not exist', () => {
        fsStub.existsSync.returns(false);

        const checkGoVendorProxy = proxyquire('../src/utils', {
            fs: fsStub,
        }).checkGoVendor;
        checkGoVendorProxy(params);

        expect(exportEnvVarStub.calledOnce).to.be.true;
        expect(exportEnvVarStub.calledWith('GO111MODULE', 'auto')).to.be.true;
    });
});


describe('checkGoVersion', () => {
    let execStub: sinon.SinonStub;

    beforeEach(() => {
        execStub = sinon.stub(step.exec, 'callOutput');
    });

    afterEach(() => {
        execStub.restore();
    });

    it('should succeed with valid go version 1.21.0', async () => {
        execStub.resolves({ exitCode: 0, stdoutContent: 'go version go1.20.0 linux/amd64' });
        await checkGoVersion();
        expect(execStub.calledOnce).to.be.true;
    });

    it('should succeed with valid go version 1.17.8', async () => {
        execStub.resolves({ exitCode: 0, stdoutContent: 'go version go1.17.8 linux/amd64' });
        await checkGoVersion();
        expect(execStub.calledOnce).to.be.true;
    });

    it('should fail with invalid go version 1.10.2', async () => {
        execStub.resolves({ exitCode: 0, stdoutContent: 'go version go1.10.2 linux/amd64' });
        try {
            await checkGoVersion();
            expect.fail('Expected an error to be thrown');
        } catch (error: any) {
            expect(error.message).to.equal('golangCodeScan only support go version >= 1.11');
        }
    });


    it('should fail with non-parsable version', async () => {
        execStub.resolves({ exitCode: 0, stdoutContent: 'invalid version' });
        try {
            await checkGoVersion();
            expect.fail('Expected an error to be thrown');
        } catch (error: any) {
            expect(error.message).to.equal('Failed to parse Go version');
        }
    });

    it('should fail with non-zero exit code', async () => {
        execStub.resolves({ exitCode: 1, stdoutContent: 'go version go1.20.0' });
        try {
            await checkGoVersion();
            expect.fail('Expected an error to be thrown');
        } catch (error: any) {
            expect(error.message).to.equal('go version check failed, please install go first (version >= 1.11)');
        }
    });

    it('should fail with undefined stdoutContent', async () => {
        execStub.resolves({ exitCode: 0, stdoutContent: undefined });
        try {
            await checkGoVersion();
            expect.fail('Expected an error to be thrown');
        } catch (error: any) {
            expect(error.message).to.equal('go version check failed, please install go first (version >= 1.11)');
        }
    });
});


describe('generateLinterArgs', () => {
    it('should generate correct linter arguments with fileList and exclusion', () => {
        const fileList = "cmd/chat/main6.go cmd/chat/main7.go ";
        const exclusion = "--skip-dirs test/ --skip-files testfile.go";
        const expectedArgs = [
            'run',
            'cmd/chat/main6.go',
            'cmd/chat/main7.go',
            '-v',
            '--exclude-use-default',
            '--out-format',
            'json',
            '--skip-dirs',
            'test/',
            '--skip-files',
            'testfile.go'
        ];

        const result = generateLinterArgs(fileList, exclusion);
        expect(result).to.deep.equal(expectedArgs);
    });

    it('should handle empty fileList', () => {
        const fileList = "";
        const exclusion = "--skip-dirs test/";
        const expectedArgs = [
            'run',
            '-v',
            '--exclude-use-default',
            '--out-format',
            'json',
            '--skip-dirs',
            'test/'
        ];

        const result = generateLinterArgs(fileList, exclusion);
        expect(result).to.deep.equal(expectedArgs);
    });

    it('should handle empty exclusion', () => {
        const fileList = "cmd/chat/main6.go cmd/chat/main7.go ";
        const exclusion = "";
        const expectedArgs = [
            'run',
            'cmd/chat/main6.go',
            'cmd/chat/main7.go',
            '-v',
            '--exclude-use-default',
            '--out-format',
            'json'
        ];

        const result = generateLinterArgs(fileList, exclusion);
        expect(result).to.deep.equal(expectedArgs);
    });

    it('should handle both empty fileList and exclusion', () => {
        const fileList = "";
        const exclusion = "";
        const expectedArgs = [
            'run',
            '-v',
            '--exclude-use-default',
            '--out-format',
            'json'
        ];

        const result = generateLinterArgs(fileList, exclusion);
        expect(result).to.deep.equal(expectedArgs);
    });

    it('should handle multiple spaces in exclusion', () => {
        const fileList = "cmd/chat/main6.go cmd/chat/main7.go";
        const exclusion = "  --skip-dirs test/   --skip-files testfile.go  ";
        const expectedArgs = [
            'run',
            'cmd/chat/main6.go',
            'cmd/chat/main7.go',
            '-v',
            '--exclude-use-default',
            '--out-format',
            'json',
            '--skip-dirs',
            'test/',
            '--skip-files',
            'testfile.go'
        ];

        const result = generateLinterArgs(fileList, exclusion);
        expect(result).to.deep.equal(expectedArgs);
    });
});



